#pragma once
#include "generator.hpp"
#include <boost/thread.hpp>
#include <boost/interprocess/sync/interprocess_semaphore.hpp>
#include "sound_out.hpp"
#include "end_of_seq.hpp"

template<typename In, typename Duration>
class Sequencer
{
public:
    typedef typename In::ReturnType ReturnType;
    Sequencer();
    typename In::ReturnType operator()();
private:
    In in_;
    Duration duration_;
    typename In::ReturnType value_;
    int time_;
};

template<typename In, typename Duration>
class StaccatoSequencer
{
public:
    typedef typename In::ReturnType ReturnType;
    StaccatoSequencer();
    typename In::ReturnType operator()();
private:
    In in_;
    Duration duration_;
    typename In::ReturnType value_;
    int time_;
    int currentDuration_;
};

template<typename In, typename Duration, typename Koeff = FloatConst<99999, 100000> >
class DampedSequencer
{
public:
    typedef typename In::ReturnType ReturnType;
    DampedSequencer();
    typename In::ReturnType operator()();
private:
    In in_;
    Duration duration_;
    Koeff koeff_;
    typename In::ReturnType value_;
    int time_;
};

template<typename T>
class Sequence
{
public:
    static const float  A = 880;
    static const float As = 880 * 1.059463094;
    static const float  B = 880 * 1.059463094 * 1.059463094;
    static const float  C = 440 * 1.059463094 * 1.059463094 * 1.059463094;
    static const float Cs = 440 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094;
    static const float  D = 440 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094;
    static const float Ds = 440 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094;
    static const float  E = 440 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094;
    static const float  F = 440 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094;
    static const float Fs = 440 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094;
    static const float  G = 440 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094;
    static const float Gs = 440 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094 * 1.059463094;

    typedef T ReturnType;
    Sequence();
    ~Sequence();
    T operator()();
    bool isRunning() const;
    Sequence<T> &operator()(T value);
protected:
    virtual void run() = 0;
    Sequence<T> &send(T value);
private:
    class Run
    {
    public:
        Run(Sequence *seq):
            seq_(seq)
        {
        }
        void operator()();
    private:
        Sequence *seq_;
    };
  
    boost::interprocess::interprocess_semaphore free_, occuped_;
    volatile T value_;
    volatile bool isRunning_;
    boost::thread thread_;

    void recv();
};

class DurationSequence
{
public:
    int ReturnType;
    DurationSequence();
    ~DurationSequence();
    int operator()();
    bool isRunning() const;
    DurationSequence &operator()(float duration);
protected:
    virtual void run() = 0;
    void setTempo(float); // bits per minutes
    DurationSequence &send(float duration);
private:
    class Run
    {
    public:
        Run(DurationSequence *seq):
            seq_(seq)
        {
        }
        void operator()();
    private:
        DurationSequence *seq_;
    };
  
    boost::interprocess::interprocess_semaphore free_, occuped_;
    volatile float duration_;
    volatile float tempo_;
    volatile bool isRunning_;
    boost::thread thread_;

    void recv();
};

template<typename In, typename Duaration>
Sequencer<In, Duaration>::Sequencer():
    time_(0)
{
    
}

template<typename In, typename Duaration>
typename In::ReturnType  Sequencer<In, Duaration>::operator()()
{
    if (time_ <= 0)
    {
        value_ = in_();
        time_ = duration_();
    }
    --time_;
    return value_;
}


template<typename In, typename Duaration>
StaccatoSequencer<In, Duaration>::StaccatoSequencer():
    time_(0)
{}

template<typename In, typename Duaration>
typename In::ReturnType  StaccatoSequencer<In, Duaration>::operator()()
{
    if (time_ <= 0)
    {
        value_ = in_();
        currentDuration_ = duration_();
        time_ = currentDuration_;
    }
    --time_;
    if (time_ > currentDuration_ / 2)
        return value_;
    else
        return 0;
}

template<typename In, typename Duaration, typename Koeff>
DampedSequencer<In, Duaration, Koeff>::DampedSequencer():
    time_(0)
{}

template<typename In, typename Duaration, typename Koeff>
typename In::ReturnType DampedSequencer<In, Duaration, Koeff>::operator()()
{
    if (time_ <= 0)
    {
        value_ = in_();
        time_ = duration_();
    }
    --time_;
    value_ *= koeff_();
    return value_;
}


template<typename T>
Sequence<T>::Sequence():
    free_(1),
    occuped_(0),
    isRunning_(true),
    thread_(Run(this))
{}

template<typename T>
Sequence<T>::~Sequence()
{
    while (isRunning_)
        recv();
    free_.post();
}


template<typename T>
T Sequence<T>::operator()()
{
    if (!isRunning_)
        throw EndOfSeq();
    recv();
    return value_;
}

template<typename T>
bool Sequence<T>::isRunning() const
{
    return isRunning_;
}

template<typename T>
Sequence<T> &Sequence<T>::send(T value)
{
    free_.wait();
    value_ = value;
    occuped_.post();
    return *this;
}

template<typename T>
Sequence<T> &Sequence<T>::operator()(T value)
{
    return send(value);
}

template<typename T>
void Sequence<T>::recv()
{
    occuped_.wait();
    free_.post();
}

template<typename T>
void Sequence<T>::Run::operator()()
{
    seq_ -> run();
    seq_ -> free_.wait();
    seq_ -> occuped_.post();
    seq_ -> isRunning_ = false;
}


DurationSequence::DurationSequence():
    free_(1),
    occuped_(0),
    isRunning_(true),
    thread_(Run(this))
{}

DurationSequence::~DurationSequence()
{
    while (isRunning_)
        recv();
    free_.post();
}


int DurationSequence::operator()()
{
    if (!isRunning_)
        throw EndOfSeq();
    recv();
    const int d = 60 * SAMPLE_RATE * duration_ / tempo_;
    return d;
}

bool DurationSequence::isRunning() const
{
    return isRunning_;
}

void DurationSequence::setTempo(float value)
{
    tempo_ = value;
}

DurationSequence &DurationSequence::send(float duration)
{
    free_.wait();
    duration_ = duration;
    occuped_.post();
    return *this;
}

DurationSequence &DurationSequence::operator()(float duration)
{
    return send(duration);
}

void DurationSequence::recv()
{
    occuped_.wait();
    free_.post();
}

void DurationSequence::Run::operator()()
{
    seq_ -> run();
    seq_ -> free_.wait();
    seq_ -> occuped_.post();
    seq_ -> isRunning_ = false;
}
